/****************************************************************************************************
 Copyright (c), 2025, Microctrl Studio.
 All rights reserved.
 
 文 件：upgrade.c
 
 描 述：
        1.flash 编程函数。
        2.跳转到 application 程序。
	    3.出厂信息固化。
	    4.固件升级。
 
 作 者：lin
 
 日 志：
       v1.0.0  2025-01-01  初始版本。
							  
****************************************************************************************************/

#include "upgrade.h"
#include "ymodem.h"
#include "bsp_cpu.h"
#include "bsp_uart.h"
#include <string.h>

int32_t file_size;
uint8_t file_name[FILE_NAME_LENGTH];
uint8_t factory_info_data[FLASH_PAGE_SIZE];
uint8_t app_code[FLASH_PAGE_SIZE];

#define MIN(a, b) ((a) < (b) ? (a) : (b))

/****************************************************************************************************
 @brief  编程一页
 
 @param  [ page_address ] FLASH 页起始地址
         [ buf          ] 待编程的数据地址
		
 @return [ true  ] 编程成功
         [ false ] 编程错误
****************************************************************************************************/
bool flash_program_page(uint32_t page_address, const uint8_t *buf)
{
	uint16_t i;
	uint32_t program_word;
	bool program_status = true;

	if(page_address < FLASH_BASE || page_address > FLASH_END) {
        return false;
    }
	
	FLASH_Unlock();
	
	if(FLASH_ErasePage(page_address) == FLASH_COMPLETE)
	{
		for(i = 0; i < FLASH_PAGE_SIZE / 4; i++)
		{
			memcpy(&program_word, buf + 4 * i, sizeof(uint32_t));
			if(FLASH_ProgramWord(page_address + 4 * i, program_word) != FLASH_COMPLETE)
			{
				program_status = false;
				break;
			}
		}
	}
	else
	{
		program_status = false;
	}
	
	FLASH_Lock();
	
	return program_status;
}

/****************************************************************************************************
 @brief  跳转到应用程序

 @param  无

 @return 无
****************************************************************************************************/
void jump_to_application(void)
{
	jump_func_typedef jump_to_app;
	
	if(((*(__IO uint32_t *)FLASH_APPLICATION_ADDRESS) & 0x2FFE0000) == 0x20000000)
	{
		__set_PRIMASK(1);
		RCC_DeInit();
				
		SysTick->CTRL = 0;
		SysTick->LOAD = 0;
		SysTick->VAL  = 0;
				
		for(uint8_t i = 0; i < 8; i++)
		{
			NVIC->ICER[i] = 0xFFFFFFFF;
			NVIC->ICPR[i] = 0xFFFFFFFF;
		}
				
		__set_PRIMASK(0);
				
		jump_to_app = (jump_func_typedef)(*(__IO uint32_t *)(FLASH_APPLICATION_ADDRESS + 4));
		__set_MSP(*(__IO uint32_t *)FLASH_APPLICATION_ADDRESS);
		__set_CONTROL(0);
		jump_to_app();
	}
	else
	{
		printf("Jump to app error.\r\n"); 
		ms_delay(1000);
		cpu_reset();
	}
}

/****************************************************************************************************
 @brief  升级处理函数
		
	   1.固件的第1页数据是设备出厂信息和IAP标志，为避免固件下载失败而导致bootloader IAP功能失效，须
         在下载完application程序，最后再固化此页数据。

	   2.固件的第2页及往后是application程序，每次擦写1K字节数据。

 @param  [ packet_data ] 数据包
         [ length      ] 数据包长度
         [ user_data   ] 包序列号

 @return [ -1 ] 写入失败
         [ 0  ] 写入成功
****************************************************************************************************/
int32_t flash_program(uint8_t *packet_data, int32_t length, void *user_data)
{
	if(length > FLASH_PAGE_SIZE) {
        return -1;
    }

	int32_t packet_num = *((int32_t *)user_data);
	
	if(packet_num == 1)
	{
		memcpy(factory_info_data, packet_data, MIN(length, FLASH_PAGE_SIZE));
	}
	else
	{
		memcpy(app_code, packet_data, length);
		if(flash_program_page(FLASH_APPLICATION_ADDRESS + 1024 * (packet_num - 2), app_code) == false)
		{
			return -1;
		}
	}
	
	return 0;
}

/****************************************************************************************************
 @brief  升级固件

         1.bootloader启动时，先检查出厂信息区域的IAP标志是否被擦除，若被擦除则进行IAP升级。
           升级完成，最后固化出厂信息和IAP标志，再跳转到运行application。
	   
         2.若不需要进行IAP，则直接跳转运行application。

 @param  无

 @return 无
****************************************************************************************************/
void upgrade_firmware(void)
{	 
	struct factory_info *factory_info = (struct factory_info *)FLASH_FACTORY_INFO_ADDRESS;
	//ms_delay(5000);
	printf("bootloader startup...\r\n");
	ms_delay(100);
	if(factory_info->firmware_upgrade_flag == 0xFFFFFFFF) 
	{
		if(ymodem_receive(file_name, &file_size, flash_program) == false) {
			return;
		}
		
		if(flash_program_page(FLASH_FACTORY_INFO_ADDRESS, factory_info_data) == false) {
			return;
		}
		
		printf("\r\n\r\n");
		printf("The file download success.\r\n");
		printf("File name: %s\r\n", file_name);
		printf("File size: %d bytes\r\n\r\n", file_size);
		ms_delay(500);
	}
		
	jump_to_application();
}
